-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

-- Description: 固件传包
local class = require 'mc.class'
local log = require 'mc.logging'
local crc16 = require 'mc.crc16'
local component_communicate = require 'domain.transport.component_communicate'

local ComponentData = class()

function ComponentData:ctor(data)
    self.firmware_data = data
    self.offset = 0
end

function ComponentData:clear()
    self.offset = 0
end

-- 固件或者header
function ComponentData:get_type()
    return self.firmware_data.Type
end

-- 固件的id
function ComponentData:get_id()
    return self.firmware_data.FirmwareId
end

-- 固件数据
function ComponentData:get_raw_data()
    return self.firmware_data.RawData
end

function ComponentData:get_crc()
    if self.crc then
        return self.crc
    end
    return crc16(self:get_raw_data())
end

-- 固件大小
function ComponentData:get_size()
    return #self.firmware_data.RawData
end

-- 偏移量
function ComponentData:get_offset()
    return self.offset
end

-- 偏移量
function ComponentData:set_offset(offset)
    self.offset = offset
end

-- 读取固件数据：等于或者小于248
function ComponentData:fetch_data()
    local offset = self:get_offset()
    local left_size = self:get_size() - offset
    if left_size == 0 then
        return nil
    end
    local fetch_size = left_size > 235 and 235 or left_size
    local data = string.sub(self:get_raw_data(), offset + 1, offset + fetch_size)
    self:set_offset(offset + fetch_size)
    return data
end

local ComponentUpgrade = class(component_communicate)

local function validate_firmware_data(data)
    if not data or not data.RawData or not data.FirmwareId or
        not data.Type then
        error('[bios]component upgrade: invalid param')
    end
end

-- data包含：固件bitmap、固件数据、固件类型
function ComponentUpgrade:write(data, system_id)
    self.system_id = system_id
    validate_firmware_data(data)
    self.component_data = ComponentData.new(data)
    self:write_firmware_data()
end

-- 整个固件发送失败，重试三次
function ComponentUpgrade:write_firmware_data()
    local ok, res
    log:notice('[bios]component upgrade: write firmware id(0x%x) start',
        self.component_data:get_id())
    for _ = 1, 3 do
        ok, res = pcall(function()
            self:write_prepare()
            log:notice('[bios]component upgrade: prepare send success')
            self:write_data()
            log:notice('[bios]component upgrade: process send success')
            self:write_finish()
            log:notice('[bios]component upgrade: finish send success')
        end)
        if ok then
            log:notice('[bios]component upgrade: write firmware id(0x%x) success',
                self.component_data:get_id())
            return
        end
        self.component_data:clear()
    end
    error(string.format('[bios]component upgrade: write firmware(%s) fail, err %s',
        self.component_data:get_type(), res))
end

function ComponentUpgrade:write_prepare()
    local msg = {
        Header = {
            DestNetFn = self.defs.netfn.OemReqNetfn,
            Cmd = self.defs.cmd.ImuCmd
        },
        Data = {
            Type = self.component_data:get_type(),
            FileSize = self.component_data:get_size(),
            CRC = self.component_data:get_crc(),
            FirmwareId = self.component_data:get_id()
        },
        SendFormat = [[<<0xDB0700:3/unit:8, 0x26, Type, 0x00, FileSize:3/unit:8, 0x03, CRC:2/unit:8, FirmwareId>>]],
        ReceiveFormat = [[<<ManuId:3/unit:8>>]],
        SystemId = self.system_id
    }
    self:retry_send_msg(msg)
end

function ComponentUpgrade:write_data()
    local offset = self.component_data:get_offset()
    local data = self.component_data:fetch_data()
    while data do
        local msg = {
            Header = {
                DestNetFn = self.defs.netfn.OemReqNetfn,
                Cmd = self.defs.cmd.ImuCmd
            },
            Data = {
                Type = self.component_data:get_type(),
                Offest = offset,
                Size = #data,
                FirmwareData = data
            },
            SendFormat = [[<<0xDB0700:3/unit:8, 0x26, Type, 0x01, Offest:3/unit:8, Size, FirmwareData/string>>]],
            ReceiveFormat = [[<<ManuId:3/unit:8>>]],
            SystemId = self.system_id
        }
        self:retry_send_msg(msg)
        offset = self.component_data:get_offset()
        data = self.component_data:fetch_data()
    end
end

function ComponentUpgrade:write_finish()
    local msg = {
        Header = {
            DestNetFn = self.defs.netfn.OemReqNetfn,
            Cmd = self.defs.cmd.ImuCmd
        },
        Data = {
            Type = self.component_data:get_type()
        },
        SendFormat = [[<<0xDB0700:3/unit:8, 0x26, Type, 0x03>>]],
        ReceiveFormat = [[<<ManuId:3/unit:8>>]],
        SystemId = self.system_id
    }
    self:retry_send_msg(msg)
end

return ComponentUpgrade